import {
  AfterViewInit,
  Component,
  ElementRef,
  HostListener,
  OnDestroy,
  OnInit,
  ViewChild,
} from '@angular/core';
import G6, * as _G6 from '@antv/g6';
import { DndService } from '@ng-dnd/core';
import insertCss from 'insert-css';
import { ConfirmationService, MessageService } from 'primeng/api';
import { fromEvent } from 'rxjs/internal/observable/fromEvent';
import { VerifyConnect } from '../../../../../app/draw/utils/connect-util';
import { v4 as uuidv4 } from 'uuid';
import { DashLine } from '../../../../../app/draw/edges/edges';
import {
  CardNode,
  getNodeThemeColor,
} from '../../../../../app/draw/nodes/nodes';
import {
  processParallelEdgesOnAnchorPoint,
  redo,
  undo,
  zoomIn,
  zoomOut,
} from '../../../../../app/draw/utils/common-util';
import { BoxWithLocation } from '../draggable/draggable-box/BoxWithLocation';
import { StageService } from '../stage.service';
import { Store } from '@ngrx/store';
import {
  SelectedGraphObjectState,
  ZoomOperationState,
} from '../../../../../app/state/editor/models/editor-model';
import { EditorActions } from '../../../../../app/state/editor/actions/editor-action';
import { DrawService } from '../../../../../app/draw/draw.service';
import {
  selectEditor,
  selectEditorZoom,
} from '../../../../../app/state/editor/selectors/editor-selector';
import { ExpandableCombo } from '../../../../../app/draw/combos/combos';
import { EDITOR_CONTEXT_MENU } from '../../../../../app/draw/contextMenu/contextMenu';
import { EDITOR_TOOLTIP } from '../../../../../app/draw/tooltip/tooltip';

G6.registerNode(
  CardNode.NODE_NAME,
  CardNode.NODE_DEFINTION,
  CardNode.EXTEND_NODE
);

G6.registerEdge(
  DashLine.EDGE_NAME,
  DashLine.EDGE_DEFINTION,
  DashLine.EXTEND_EDGE
);

G6.registerCombo(
  ExpandableCombo.COMBOM_NAME,
  ExpandableCombo.COMNOM_DEFINTION,
  ExpandableCombo.EXTEND_COMBOM
);

insertCss(`
  .g6-minimap-container {
    border: 1px solid #eccc68;
  }
  .g6-minimap-viewport {
    border: 2px solid #eccc68;
    outline:  2px solid  #eccc68 !important
  }
`);
@Component({
  selector: 'dws-graph-container',
  templateUrl: './graph-container.component.html',
  styleUrls: ['./graph-container.component.scss'],
  providers: [ConfirmationService, MessageService],
})
export class GraphContainerComponent
  implements OnInit, OnDestroy, AfterViewInit {
  @ViewChild('mountNode', { static: false })
  canvasParent!: ElementRef<HTMLDivElement>;
  @ViewChild('minimapContainer', { static: false })
  minimapContainer!: ElementRef<HTMLDivElement>;

  graph!: _G6.Graph;

  boxTarget = this.dnd.dropTarget<BoxWithLocation>('BOX', {
    drop: (monitor) => {
      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      const delta = monitor.getDifferenceFromInitialOffset()!;
      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      const item = monitor.getItem()!;

      const xPosition = monitor.getClientOffset().x;
      const yPosition = monitor.getClientOffset().y;
      // this.moveBox(item.id, item.left + delta.x, item.top + delta.y);
      console.log(monitor.getInitialSourceClientOffset());
      console.log(monitor.getSourceClientOffset());
      console.log(monitor.getInitialClientOffset());
      console.log(monitor.getClientOffset());

      this.addNode(xPosition, yPosition, item.type, item.name);
    },
  });

  addNode(x: number, y: number, unitType: string, title: string) {
    const s = this.graph.getPointByClient(x, y);
    console.log(x, y);
    console.log(s);
    this.graph.addItem('node', {
      id: uuidv4(),
      title,
      unitType,
      themeColor: getNodeThemeColor(unitType),
      nodeType: 'a',
      x: s.x,
      y: s.y,
      animation: unitType === 'INIT_DU' ? true : false,
      entryPoint: unitType === 'DPU' ? true : false,
    });
  }

  initGraph() {
    const snapLine = new G6.SnapLine();

    let sourceAnchorIdx;
    let targetAnchorIdx;
    let sourceAnchor;
    let targetAnchor;
    const grid = new G6.Grid();

    const minimap = new G6.Minimap({
      size: [50, 85],
      container: this.minimapContainer.nativeElement,
    });
    const width = this.canvasParent.nativeElement.clientWidth;
    const height = window.innerHeight - 150;
    const graph = new G6.Graph({
      container: 'mountNode',
      width,
      height,
      fitView: true,
      // 设置为true，启用 redo & undo 栈功能
      enabledStack: true,
      groupByTypes: false,
      // Configure the combos globally
      defaultCombo: {
        // The type of the combos. You can also assign type in the data of combos
        type: 'cCircle',
        labelCfg: {
          refY: 2,
          style: {
            stroke: '#000000',
            fill: '#ecf0f1',
            fontSize: 20
          },
        },
        // ... Other global configurations for combos
      },
      modes: {
        default: [
          'brush-select',
          'drag-combo',
          // 'drag-node',
          'zoom-canvas',
          'drag-canvas', // config the shouldBegin for drag-node to avoid node moving while dragging on the anchor-point circles
          {
            type: 'drag-node',
            shouldBegin: (e) => {
              if (e.target.get('name') === 'anchor-point') {
                return false;
              }
              return true;
            },
          },
          // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles
          {
            type: 'create-edge',
            trigger: 'drag', // set the trigger to be drag to make the create-edge triggered by drag
            shouldBegin: (e) => {
              // avoid beginning at other shapes on the node
              if (e.target && e.target.get('name') !== 'anchor-point') {
                return false;
              }
              sourceAnchorIdx = e.target.get('anchorPointIdx');
              sourceAnchor = e.target;
              e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle
              return true;
            },
            shouldEnd: (e) => {
              // avoid ending at other shapes on the node
              if (e.target && e.target.get('name') !== 'anchor-point') {
                return false;
              }
              if (e.target) {
                targetAnchorIdx = e.target.get('anchorPointIdx');
                targetAnchor = e.target;
                e.target.set('links', e.target.get('links') + 1); // cache the number of edge connected to this anchor-point circle
                return true;
              }
              targetAnchorIdx = undefined;
              targetAnchor = e.target;
              return true;
            },
          },
        ],
      },
      defaultNode: {
        type: 'card-node',
      },
      defaultEdge: {
        type: 'cubic-vertical',
        style: {
          stroke: '#F6BD16',
          lineWidth: 2,
          endArrow: true,
        },
      },
      plugins: [minimap, grid, snapLine],
    });

    graph.addPlugin(EDITOR_CONTEXT_MENU(graph));
    graph.addPlugin(EDITOR_TOOLTIP(graph));
    graph.on('aftercreateedge', (e) => {
      // update the sourceAnchor and targetAnchor for the newly added edge
      const drawEdge = e.edge as _G6.Edge;

      console.log(sourceAnchor);
      console.log(targetAnchor);
      if (VerifyConnect(sourceAnchor, targetAnchor)) {
        if (drawEdge.getSource().getID() === drawEdge.getTarget().getID()) {
          this.messageService.add({
            key: 'tc',
            severity: 'warn',
            summary: 'Warn',
            detail: `You can't reference self here`,
          });

          setTimeout(() => {
            graph.removeItem(drawEdge);
          });
          return;
        }
        if (sourceAnchor.cfg.anchorType === 'executeExit') {
          graph.updateItem(drawEdge, {
            sourceAnchor: sourceAnchorIdx,
            targetAnchor: targetAnchorIdx,
            connectType: 'EXECUTE_CHAIN',
          });
        } else if (sourceAnchor.cfg.anchorType === 'INIT_DU_OUT_POINT') {
          graph.updateItem(drawEdge, {
            sourceAnchor: sourceAnchorIdx,
            targetAnchor: targetAnchorIdx,
            connectType: 'INIT_DU_OUT_POINT',
          });
        } else {
          graph.updateItem(drawEdge, {
            sourceAnchor: sourceAnchorIdx,
            targetAnchor: targetAnchorIdx,
          });
        }
        // update the curveOffset for parallel edges
        const edges = graph.save().edges;
        processParallelEdgesOnAnchorPoint(edges);
        graph.getEdges().forEach((edge, i) => {
          graph.updateItem(edge, {
            curveOffset: edges[i].curveOffset,
            curvePosition: edges[i].curvePosition,
          });
        });
      } else {
        this.messageService.add({
          key: 'tc',
          severity: 'warn',
          summary: 'Warn',
          detail: `You can't conntect this two`,
        });

        setTimeout(() => {
          graph.removeItem(drawEdge);
        });
        return;
      }
    });

    // after drag from the first node, the edge is created, update the sourceAnchor
    graph.on('afteradditem', (e) => {
      if (e.item && e.item.getType() === 'edge') {
        graph.updateItem(e.item, {
          sourceAnchor: sourceAnchorIdx,
        });
      }
    });

    // if create-edge is canceled before ending, update the 'links' on the anchor-point circles
    graph.on('afterremoveitem', (e) => {
      if (e.item && (e.item as any).source && (e.item as any).target) {
        const sourceNode = graph.findById((e.item as any).source);
        const targetNode = graph.findById((e.item as any).target);
        // eslint-disable-next-line @typescript-eslint/no-shadow
        const { sourceAnchor, targetAnchor } = e.item as any;
        if (sourceNode && !isNaN(sourceAnchor)) {
          const sourceAnchorShape = sourceNode
            .getContainer()
            .find(
              (ele) =>
                ele.get('name') === 'anchor-point' &&
                ele.get('anchorPointIdx') === sourceAnchor
            );
          if (sourceAnchorShape) {
            sourceAnchorShape.set('links', sourceAnchorShape.get('links') - 1);
          }
        }
        if (targetNode && !isNaN(targetAnchor)) {
          const targetAnchorShape = targetNode
            .getContainer()
            .find(
              (ele) =>
                ele.get('name') === 'anchor-point' &&
                ele.get('anchorPointIdx') === targetAnchor
            );
          targetAnchorShape.set('links', targetAnchorShape.get('links') - 1);
        }
      }
    });

    // some listeners to control the state of nodes to show and hide anchor-point circles
    graph.on('node:mouseenter', (e) => {
      graph.setItemState(e.item, 'showAnchors', true);
    });
    graph.on('node:mouseleave', (e) => {
      graph.setItemState(e.item, 'showAnchors', false);
    });
    graph.on('node:dragenter', (e) => {
      graph.setItemState(e.item, 'showAnchors', true);
    });
    graph.on('node:dragleave', (e) => {
      graph.setItemState(e.item, 'showAnchors', false);
    });
    graph.on('node:dragstart', (e) => {
      graph.setItemState(e.item, 'showAnchors', true);
    });
    graph.on('node:dragout', (e) => {
      graph.setItemState(e.item, 'showAnchors', false);
    });
    graph.on('edge:click', (e) => {
      const egde = e.item as _G6.Edge;
      graph.setItemState(egde, 'selected', true);

      // graph.updateItem(egde, {
      //   style: { lineWidth: 3 ,stroke: '#74b9ff', strokeOpacity: 0.5 },
      // });
      this.drawService.changeUniqueSelect(graph, egde);
      // eslint-disable-next-line no-underscore-dangle
      this._store.dispatch(
        EditorActions.selectChange({ payload: { type: 'EDGE', obj: {} } })
      );
    });
    graph.on('canvas:click', (e) => {
      graph.getCombos().forEach((combo) => {
        combo.getEdges().forEach((edge) => {
          graph.clearItemStates(edge);

        });
        graph.clearItemStates(combo);
      });
      // eslint-disable-next-line no-underscore-dangle
      this._store.dispatch(
        EditorActions.selectChange({ payload: { type: 'CANVAS', obj: {} } })
      );
    });
    graph.on('node:click', (e) => {
      const node = e.item;
      // const model = node.getModel();
      // console.log(model);
      // model.animation = false;
      // node.update(model);
      // node.draw();
      graph.updateItem(node, {
        style: { lineWidth: 8, stroke: '#5f95ff', strokeOpacity: 0.7 },
      });
      this.drawService.changeUniqueSelect(graph, node);
      // eslint-disable-next-line no-underscore-dangle
      this._store.dispatch(
        EditorActions.selectChange({ payload: { type: 'NODE', obj: {} } })
      );
    });
    graph.on('delete-node:click', (evt) => {
      graph.remove(evt.item);
    });

    graph.on('nodeselectchange', (e) => {
      console.log('======开始选中========');
      console.log(e.selectedItems, e.select);
      if (e.selectedItems && (e.selectedItems as any).nodes && e.select) {
        const nodes = (e.selectedItems as any) as _G6.Node[];
        nodes.forEach((n) => {
          graph.updateItem(n, {
            style: { lineWidth: 8, stroke: '#5f95ff', strokeOpacity: 0.7 },
          });
        });
      }
    });

    // collapse/expand when click the marker
    graph.on('combo-marker-shape:dblclick', (e) => {
      // graph.collapseExpandCombo(e.item.getModel().id);
      const combo = e.item as _G6.Combo;

      graph.collapseExpandCombo(combo);

      if (graph.get('layout')) { graph.layout(); }
      else { graph.refreshPositions(); }
      this.canvasParent.nativeElement.click();
      this.canvasParent.nativeElement.click();
    });

    graph.on('combo:mouseenter', (evt) => {
      const { item } = evt;
      graph.setItemState(item, 'active', true);
    });

    graph.on('combo:mouseleave', (evt) => {
      const { item } = evt;
      graph.setItemState(item, 'active', false);
    });
    graph.on('combo:click', (evt) => {
      const { item } = evt;
      graph.setItemState(item, 'selected', true);

      this.drawService.changeUniqueSelect(graph, item);
      // eslint-disable-next-line no-underscore-dangle
      this._store.dispatch(
        EditorActions.selectChange({ payload: { type: 'COMBO', obj: {} } })
      );
    });

    graph.on('aftercollapseexpandcombo', (e) => {
      const allCombos: _G6.ICombo[] = graph.getCombos();
      allCombos.forEach(combo => {
        const outerEdges = combo.getOutEdges();
        outerEdges.forEach(edge => {
          graph.update(edge, { style: { stroke: '#9b59b6' } });
        });
      });
    });

    const data = {
      combos: [{ id: 'combo1', label: 'Combo 1' }],
    };
    graph.data(data);
    graph.render();
    this.graph = graph;
  }

  // eslint-disable-next-line @typescript-eslint/member-ordering
  constructor(
    private dnd: DndService,
    private stageService: StageService,
    private messageService: MessageService,
    // eslint-disable-next-line @ngrx/no-typed-global-store
    private _store: Store<SelectedGraphObjectState>,
    private drawService: DrawService,
    private _zoomStore: Store<ZoomOperationState>
  ) {
    // eslint-disable-next-line no-underscore-dangle, @ngrx/avoid-mapping-selectors
    this._store.select(selectEditor).subscribe((data) => {
      if (data.type === 'CANVAS') {
        this.drawService.changeUniqueSelect(this.graph);
      }
    });
  }

  ngAfterViewInit(): void {
    this.initGraph();
    this.stageService.stageSizeChange.subscribe((_msg) => {
      if (this.graph) {
        this.graph.changeSize(
          this.canvasParent.nativeElement.clientWidth,
          window.innerHeight - 150
        );
      }
    });
  }

  ngOnInit() {
    fromEvent(window, 'resize').subscribe((_data) => {
      console.log('=====window size changed=======');
      if (this.graph) {
        this.graph.changeSize(
          this.canvasParent.nativeElement.clientWidth,
          window.innerHeight - 150
        );
      }
    });
    // eslint-disable-next-line no-underscore-dangle
    this._zoomStore.select(selectEditorZoom).subscribe((data) => {
      console.log('======accept zoom operation===');
      console.log(data);
      switch (data.type) {
        case 'IN':
          this.zoomIn();
          break;
        case 'OUT':
          this.zoomOut();
          break;
      }
    });
  }

  ngOnDestroy() {
    this.boxTarget.unsubscribe();
  }

  undo() {
    undo(this.graph);
  }
  redo() {
    redo(this.graph);
  }
  zoomOut() {
    zoomOut(this.graph);
  }
  zoomIn() {
    zoomIn(this.graph);
  }
}
